﻿using System;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using ExchangeFilesPharmWinService.ExchangeFiles;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Timers;
using System.Security.Cryptography;

namespace ExchangeFilesPharmWinService
{
    public partial class ExchangeFilesService : ServiceBase
    {
        private int clientId;
        private string curDir;
        private System.Timers.Timer timer;
        private int perMin = 1;
        private string pathIn;
        private string pathOut;
        private string pathIn_map;
        private string pathOut_map;
        private LogClass log = new LogClass();

        public ExchangeFilesService()
        {
            InitializeComponent();
            if (!EventLog.SourceExists("EFSSource"))
            {
                EventLog.CreateEventSource("EFSSource", "EFSLog");
            }
            eventLog1.Source = "EFSSource";
            eventLog1.Log = "EFSLog";
            eventLog1.MaximumKilobytes = 1024;
            eventLog1.ModifyOverflowPolicy(OverflowAction.OverwriteAsNeeded, 5);
        }

        protected override void OnStart(string[] args)
        {
            try
            {
                ExchangeFilesClient client = new ExchangeFilesClient();
                try
                {
                    curDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                    clientId = Convert.ToInt32(config.AppSettings.Settings["clientid"].Value);
                    pathIn = config.AppSettings.Settings["path_in"].Value.ToString();
                    pathOut = config.AppSettings.Settings["path_out"].Value.ToString();
                    pathIn_map = config.AppSettings.Settings["path_map_in"].Value.ToString();
                    pathOut_map = config.AppSettings.Settings["path_map_out"].Value.ToString();
                    perMin = Convert.ToInt32(config.AppSettings.Settings["periodMinute"].Value);
                    if (!Directory.Exists(pathIn)) Directory.CreateDirectory(pathIn);
                    if (!Directory.Exists(pathOut)) Directory.CreateDirectory(pathOut);
                    if (!Directory.Exists(pathIn_map)) Directory.CreateDirectory(pathIn_map);
                    if (!Directory.Exists(pathOut_map)) Directory.CreateDirectory(pathOut_map);
                    string ip = new WorkPlace().GetIP();
                    int pharmCode = new WorkPlace().GetPharmCode();
                    if (clientId == 0)
                    {
                        log.LogIns(1);
                        string[] filelist = Directory.GetFiles(curDir, "*.exe");

                        DataTable files = new DataTable("files");
                        files.Columns.Add("fileid");
                        files.Columns.Add("filename");
                        files.Columns.Add("fileversion");
                        foreach (string s in filelist)
                        {
                            object[] row = new object[3] { null, Path.GetFileName(s), FileVersionInfo.GetVersionInfo(s).FileVersion };
                            files.Rows.Add(row);
                        }
                        clientId = client.NewClient(ip, files, pharmCode);
                        config.AppSettings.Settings["clientid"].Value = clientId.ToString();
                        config.AppSettings.Settings["ip"].Value = ip;
                        config.AppSettings.Settings["pharmcode"].Value = pharmCode.ToString();
                        config.Save(ConfigurationSaveMode.Modified);
                        ConfigurationManager.RefreshSection("appSettings");
                    }
                    else
                    {
                        log.LogIns(2);
                        if (ip != "")
                            if (config.AppSettings.Settings["ip"].Value != ip)
                            {
                                client.ClientIPUpd(clientId, ip);
                                config.AppSettings.Settings["ip"].Value = ip;
                                config.Save(ConfigurationSaveMode.Modified);
                                ConfigurationManager.RefreshSection("appSettings");
                            }
                        if (config.AppSettings.Settings["pharmcode"].Value != pharmCode.ToString() && pharmCode > 0)
                        {
                            client.ClientPharmCodeUpd(clientId, pharmCode);
                            config.AppSettings.Settings["pharmcode"].Value = pharmCode.ToString();
                            config.Save(ConfigurationSaveMode.Modified);
                            ConfigurationManager.RefreshSection("appSettings");
                        }
                    }
                }
                catch (Exception ex)
                {
                    log.LogIns(3, ex.Message);
                }
                finally
                {
                    client.Close();
                    try
                    {
                        log.LogIns(12);
                        timer = new System.Timers.Timer(perMin * 60000);
                        timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
                        timer.Enabled = true;
                        if (timer.Enabled) log.LogIns(13);
                        eventLog1.WriteEntry("Служба запущена");
                    }
                    catch (Exception ex)
                    {
                        log.LogIns(14, ex.Message);
                    }
                }
            }
            catch (Exception ex)
            {
                eventLog1.WriteEntry("Ошибка при запуске службы!\nМетод: OnStart()\n" + ex.Message, EventLogEntryType.Error);
            }
        }

        protected override void OnStop()
        {
            try
            {              
                log.LogIns(6);
                eventLog1.WriteEntry("Служба остановлена");
            }
            catch (Exception ex)
            {
                eventLog1.WriteEntry("Ошибка при остановке службы!\nМетод: OnStop()\n" + ex.Message, EventLogEntryType.Error);
            }
        }

        private void OnTimedEvent(object source, ElapsedEventArgs e)
        {
            try
            {
                timer.Enabled = false;
                log.LogIns(15);
                ExchangeFilesClient client = new ExchangeFilesClient();

                try
                {
                    string ip = new WorkPlace().GetIP();
                    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                    if (config.AppSettings.Settings["ip"].Value != ip)
                    {
                        client.ClientIPUpd(clientId, ip);
                        config.AppSettings.Settings["ip"].Value = ip;
                        config.Save(ConfigurationSaveMode.Modified);
                        ConfigurationManager.RefreshSection("appSettings");
                    }
                    client.IAmFree(clientId);
                }
                catch (Exception)
                {
                }
                finally
                {
                    client.Close();
                }

                bool fl = false;
                string except = "";
                try
                {
                    var filesServer = GetFilesServer();
                    if (filesServer.Length > 0)
                        foreach (FileInfoDC file in filesServer)
                            DownloadFile(file,false);
                }
                catch(Exception ex)
                {
                    except = "Ошибка при загрузке файлов: " + ex.Message;
                    fl = true;
                }
                try
                {
                    var filesClient = GetFilesClient();
                    if (filesClient.Length > 0)
                        foreach (FileInfoDC file in filesClient)
                        {
                            SendFile(file, true);
                        }
                }
                catch (Exception ex)
                {
                    if (except != "") except = except + ", ";
                    except = "Ошибка при заливке файлов на сервер: " + ex.Message;
                    fl = true;
                }
                
                SearchUpdate();
                if (!fl) log.LogIns(27);
                else log.LogIns(34, except);
            }
            catch (Exception ex)
            {
                log.LogIns(34, ex.Message);
            }
            finally
            {
                timer.Enabled = true;
            }
        }

        private FileInfoDC[] GetFilesServer()
        {
            log.LogIns(7);
            var files = new FileInfoDC[0];
            ExchangeFilesClient client = new ExchangeFilesClient();
            try
            {
                files = client.GetFilesNamesOut(clientId);
                if (files.Length == 0) log.LogIns(17);
                else log.LogIns(18);
            }
            catch (Exception ex)
            {
                log.LogIns(16, ex.Message);
            }
            finally
            {
                client.Close();
            }
            return files;
        }

        private FileInfoDC[] GetFilesClient()
        {
            log.LogIns(19);
            var files = new FileInfoDC[0];
            ExchangeFilesClient client = new ExchangeFilesClient();
            try
            {
                var chunkLeight = client.GetChunkLeight();
                var result_map = Directory.GetFiles(pathOut_map);
                if (result_map.Length > 0)
                {
                    files = new FileInfoDC[result_map.Length];
                    for (var i = 0; i < result_map.Length; i++)
                    {
                        files[i] = new FileInfoDC()
                        {
                            Path = result_map[i],//Path.GetFileName(result[i]),
                            Size = new FileInfo(result_map[i]).Length,
                            CountChunk = (int)Math.Ceiling(new FileInfo(result_map[i]).Length / (double)chunkLeight),
                            MD5Checksum = ComputeMD5Checksum(result_map[i]),
                            MapFile = true,
                            ForAll = false
                        };
                    }
                    log.LogIns(20);
                }

                var result = Directory.GetFiles(pathOut);
                if (result.Length > 0)
                {
                    files = new FileInfoDC[result.Length];
                    Array.Resize(ref files, files.Length + result.Length);
                    for (var i = 0; i < result.Length; i++)
                    {
                        files[files.Length - result.Length + i] = new FileInfoDC()
                        {
                            Path = result[i],//Path.GetFileName(result[i]),
                            Size = new FileInfo(result[i]).Length,
                            CountChunk = (int)Math.Ceiling(new FileInfo(result[i]).Length / (double)chunkLeight),
                            MD5Checksum = ComputeMD5Checksum(result[i]),
                            MapFile = false,
                            ForAll = false
                        };
                    }
                    log.LogIns(20);
                }
                
                if(result.Length == 0 && result_map.Length == 0) log.LogIns(21);
            }
            catch (Exception ex)
            {
                log.LogIns(22, ex.Message);
            }
            finally
            {
                client.Close();
            }
            return files;
        }

        static string ComputeMD5Checksum(string path)
        {
            using (FileStream fs = System.IO.File.OpenRead(path))
            {
                byte[] fileData = new byte[fs.Length];
                fs.Read(fileData, 0, (int)fs.Length);
                byte[] checkSum = new MD5CryptoServiceProvider().ComputeHash(fileData);
                return BitConverter.ToString(checkSum).Replace("-", String.Empty);
            }
        }

        static string ComputeMD5Checksum(byte[] bytes)
        {
            var checkSum = new MD5CryptoServiceProvider().ComputeHash(bytes);
            return BitConverter.ToString(checkSum).Replace("-", String.Empty);
        }

        private bool DownloadFile(FileInfoDC file, bool update)
        {
            ExchangeFilesClient client = new ExchangeFilesClient();
            var ret = false;
            var fileName = Path.GetFileName(file.Path);
            var currFile = "";
            if (update) currFile = curDir + @"\" + fileName + "$";
            else
            {
                if (file.MapFile) currFile = pathIn_map + @"\" + fileName + "$";
                else currFile = pathIn + @"\" + fileName + "$";
            }
            var chunkLeight = client.GetChunkLeight();
            var countChunk = (int)Math.Ceiling(file.Size / (double)chunkLeight);

            if (File.Exists(currFile)) File.Delete(currFile);
            if (file.ForAll && File.Exists(currFile.TrimEnd('$')) && !update)
            {
                ret = true;
            }
            else
            {
                log.LogIns(8, Path.GetFileName(file.Path), file.Size, file.MD5Checksum);
                var stream = File.Create(currFile);
                try
                {
                    for (var i = 0; i < countChunk; i++)
                    {
                        var chunk = client.GetChunk(file.Path, i);
                        var chunkBytes = Convert.FromBase64String(chunk.Chunk);
                        var curFileLengthNew = new FileInfo(currFile).Length + chunkBytes.Length;
                        if (chunkBytes.Length == (int)chunkLeight ||
                            ((long)chunkBytes.Length < chunkLeight && i == file.CountChunk - 1 && curFileLengthNew == file.Size))
                            if (ComputeMD5Checksum(chunkBytes) == chunk.MD5Checksum)
                            {
                                stream.Write(chunkBytes, 0, chunkBytes.Length);
                                stream.Flush();
                                if (new FileInfo(currFile).Length == file.Size)
                                {
                                    stream.Close();
                                    log.LogIns(10, file.Path, file.Size, file.MD5Checksum, "Скаченный файл: " + currFile);
                                    if (ComputeMD5Checksum(currFile) == file.MD5Checksum)
                                        ret = true;
                                }
                            }
                            else
                            {
                                i--;
                                log.LogIns(10, file.Path, file.Size, file.MD5Checksum);
                            }
                        else
                        {
                            i--;
                            log.LogIns(10, file.Path, file.Size, file.MD5Checksum);
                        }
                    }
                    if (ret)
                    {
                        if (File.Exists(currFile.TrimEnd('$'))) File.Delete(currFile.TrimEnd('$'));
                        File.Move(currFile, currFile.TrimEnd('$'));
                        log.LogIns(9, file.Path, file.Size, file.MD5Checksum,FileVersionInfo.GetVersionInfo(currFile.TrimEnd('$')).ToString());
                        if (!file.ForAll)
                        {
                            log.LogIns(28, file.Path, file.Size, file.MD5Checksum);
                            if (client.DelFileFromServOut(file.Path)) log.LogIns(29, file.Path, file.Size, file.MD5Checksum);
                            else log.LogIns(30, file.Path, file.Size, file.MD5Checksum);
                        }
                    }
                    else log.LogIns(10, file.Path, file.Size, file.MD5Checksum);
                }
                catch (Exception ex)
                {
                    log.LogIns(10, file.Path, file.Size, file.MD5Checksum, ex.Message);
                }
                finally
                {
                    stream.Close();
                    client.Close();
                }
            }
            return ret;
        }

        private bool SendFile(FileInfoDC file, bool del)
        {
            var ret = false;
            var fileName = new FileInfo(file.Path).Name;

            var client = new ExchangeFilesClient();
            var pathInServ = "";
            var fileInServer = "";
            var fidc = new FileInfoDC();
            try
            {
                pathInServ = client.GetPathsFromServerIN(clientId, file.MapFile);
                fileInServer = pathInServ + @"\" + fileName;
                if (pathInServ != "")
                {
                    fidc = client.FileExists(fileInServer);
                    var fidcname = Path.GetFileName(fidc.Path);
                    if (fileName == fidcname && file.MD5Checksum == fidc.MD5Checksum && file.Size == fidc.Size)
                    {
                        if (del)
                        {
                            log.LogIns(31, file.Path, file.Size, file.MD5Checksum);
                            try
                            {
                                File.Delete(file.Path);
                                log.LogIns(32, file.Path, file.Size, file.MD5Checksum);
                            }
                            catch (Exception ex)
                            {
                                log.LogIns(33, file.Path, file.Size, file.MD5Checksum, ex.Message);
                            }
                        }
                        client.Close();
                    }
                    else
                    {
                        log.LogIns(23, fileName, file.Size, file.MD5Checksum);
                        var currFile = file.Path;
                        var chunkLeight = client.GetChunkLeight();
                        var currFileLength = file.Size;
                        var countChunk = (int)Math.Ceiling(currFileLength / (double)chunkLeight);
                        var stream = File.OpenRead(currFile);
                        try
                        {
                            var firstChunk = true;
                            var lastChunk = false;
                            for (var i = 0; i < countChunk; i++)
                            {
                                if (i > 0) firstChunk = false;
                                stream.Position = i * (int)chunkLeight;
                                if (i == countChunk - 1)
                                {
                                    chunkLeight = currFileLength - (countChunk - 1) * chunkLeight;
                                    lastChunk = true;
                                }
                                var sendBytes = new byte[chunkLeight];
                                stream.Read(sendBytes, 0, (int)chunkLeight);

                                if (!client.SendChunk(fileInServer, file.MapFile, i, Convert.ToBase64String(sendBytes), sendBytes.Length, ComputeMD5Checksum(sendBytes), firstChunk, lastChunk))
                                {
                                    i--;
                                    log.LogIns(26, file.Path, file.Size, file.MD5Checksum);
                                }
                            }
                            ret = true;
                            stream.Close();
                            if (client.DelFileFromClientOut(new FileInfoDC() { Path = fileInServer, Size = file.Size, MD5Checksum = file.MD5Checksum }))
                            {
                                log.LogIns(24, file.Path, file.Size, file.MD5Checksum);
                                if (del)
                                {
                                    log.LogIns(31, file.Path, file.Size, file.MD5Checksum);
                                    try
                                    {
                                        File.Delete(currFile);
                                        log.LogIns(32, file.Path, file.Size, file.MD5Checksum);
                                    }
                                    catch (Exception ex)
                                    {
                                        log.LogIns(33, file.Path, file.Size, file.MD5Checksum, ex.Message);
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            log.LogIns(25, file.Path, file.Size, file.MD5Checksum, ex.Message);
                        }
                        finally
                        {
                            stream.Close();
                            client.Close();
                        }
                    }
                }
                else
                {
                    log.LogIns(34);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("SendFile(FileInfoDC file, bool del)\n" + ex.Message);
            }
            return ret;
        }

        private void SearchUpdate()
        {
            log.LogIns(35);
            var client = new ExchangeFilesClient();
            string currfile = "";
            try
            {
                FileNameVersion fnv = (FileNameVersion)client.GetInstallerInfo();
                if (fnv.FileName != "")
                {
                    //Проверка обновлений;
                    currfile = curDir + @"\" + Path.GetFileName(fnv.FileName);
                    string currvers = FileVersionInfo.GetVersionInfo(currfile).FileVersion;
                    if (currvers != fnv.FileVersion)
                    {
                        log.LogIns(38, "Файлна клиенте:" + currfile + ";\tВерсия на клиенте:" + currvers + ";\tАктуальная версия:" + fnv.FileVersion);
                        //"\nЗагрузка нового установщика";
                        bool upd = false;
                        var files = client.GetUpdFiles();
                        foreach (FileInfoDC file in files)
                            if (Path.GetFileName(file.Path) == Path.GetFileName(fnv.FileName))
                            {
                                log.LogIns(39);
                                KillProcess(Path.GetFileNameWithoutExtension(file.Path));
                                file.ForAll = true;
                                upd = DownloadFile(file,true);
                                break;
                            }
                        //"\nУстановщик загружен";

                        if (upd)
                        {
                            log.LogIns(40);
                            client.ClientFileVersionUpd(clientId, Path.GetFileName(fnv.FileName), FileVersionInfo.GetVersionInfo(currfile).FileVersion);
                        }
                        else log.LogIns(44, "Установщик не был обновлен, возможно, по причине того что он был запущен в момент обновления");
                        //"\nЗапуск установщика";
                        //Process.Start(currfile, "-upd");
                    }
                    FileNameVersion currentservice = (FileNameVersion)client.GetWinServiceInfo();
                    string currservice = curDir + @"\" + Path.GetFileName(currentservice.FileName);
                    string currservicevers = FileVersionInfo.GetVersionInfo(currservice).FileVersion;
                    if (currentservice.FileVersion != currservicevers)
                    {
                        log.LogIns(37);
                        log.LogIns(43);
                        Process.Start(currfile, "-upd");
                    }
                    if (currvers == fnv.FileVersion && currentservice.FileVersion == currservicevers) log.LogIns(36);
                }
            }
            catch (Exception excep)
            {
                log.LogIns(44, currfile + "\n" + excep.Message);
            }
            finally
            {
                client.Close();
            }
        }

        private void KillProcess(string procName)
        {
            Process[] process = Process.GetProcessesByName(procName);
            foreach (Process p in process) p.Close();
        }
    }
}
